import 'dart:async';
import 'dart:io';

import 'package:flutter_baidu_mapapi_base/flutter_baidu_mapapi_base.dart';
import 'package:flutter_baidu_mapapi_map/flutter_baidu_mapapi_map.dart';
import 'package:flutter_bmflocation/flutter_bmflocation.dart';

import '../config/map_config.config.dart';
import '../models/map_location.model.dart';
import './app_permission_service.service.dart';

// typedef LocationChanged = void Function(double latitude, double longitude);
typedef LocationChanged = void Function(MapLocation location);

/// Provides map location service and notifies caller once location changed.
/// https://lbsyun.baidu.com/skins/MySkin/resources/iframs/coordinate.html
/// [continuousPositioningInterval] is in millionseconds.
class MapLocationService {
  MapLocationService({
    this.locationChanged,
    this.coordType = BMFLocationCoordType.gcj02,
    this.continuousPositioning = false,
    this.continuousPositioningInterval = 1000,
  }) {
    _locationFlutterPlugin.seriesLocationCallback(callback: _onLcationChanged);
  }

  final BMFLocationCoordType coordType;
  final bool continuousPositioning;
  final int continuousPositioningInterval;

  final LocationChanged? locationChanged;

  final LocationFlutterPlugin _locationFlutterPlugin = LocationFlutterPlugin();

  bool _isFirstLocation = true;

  BMFCoordinate? _lastLocationCoordinate;

  /// IMPORTANT!!!
  /// Must Call [MapLocationService().init()] in first view.
  /// DO NOT only call it right before locating action. Otherwise it won't work or even crash your app in release build.
  ///
  /// Do NOT await on these Baidu Map plugin methods, [locationFlutterPlugin.setAgreePrivacy(true)], [locationFlutterPlugin.authAK(ak)].
  /// Otherwise code will NOT proceed.
  Future<void> init() async {
    // final locationFlutterPlugin = LocationFlutterPlugin();
    _locationFlutterPlugin.setAgreePrivacy(true);
    BMFMapSDK.setAgreePrivacy(true);

    // Setting AK code only works for iOS.
    // For android please config AK in android\app\src\main\AndroidManifest.xml.
    if (Platform.isIOS) {
      _locationFlutterPlugin.authAK(MapConfig.iosKey!);

      BMFMapSDK.setApiKeyAndCoordType(
          MapConfig.iosKey!, _parseFromBMFLocationCoordType(coordType));
    } else if (Platform.isAndroid) {
      await BMFAndroidVersion.initAndroidVersion();
      BMFMapSDK.setCoordType(_parseFromBMFLocationCoordType(coordType));
    }
  }

  void dispose() {
    _locationFlutterPlugin.stopLocation();
  }

  /// Starts locating user current position.
  Future<void> startLocation() async {
    if (await _requestPermissions()) {
      await _initLocationOptions();
      await _locationFlutterPlugin.startLocation();
    }
  }

  /// IMPORTANT!!!
  /// You cannot locate continuously without setting these options.
  Future<void> _initLocationOptions() async {
    final iosOptionMap = _getIOSOptions().getMap();
    final androidOptionMap = _getAndroidOptions().getMap();

    await _locationFlutterPlugin.prepareLoc(androidOptionMap, iosOptionMap);
  }

  BaiduLocationAndroidOption _getAndroidOptions() {
    return BaiduLocationAndroidOption(
      coordType: coordType,
      locationMode: BMFLocationMode.hightAccuracy,
      isNeedAddress: true,
      isNeedAltitude: true,
      isNeedLocationPoiList: true,
      isNeedNewVersionRgc: true,
      isNeedLocationDescribe: true,
      openGps: true,
      scanspan: continuousPositioningInterval,
    );
  }

  BaiduLocationIOSOption _getIOSOptions() {
    return BaiduLocationIOSOption(
      coordType: coordType,
      desiredAccuracy: BMFDesiredAccuracy.best,
      allowsBackgroundLocationUpdates: true,
      pausesLocationUpdatesAutomatically: false,
    );
  }

  /// Checks on device location permission.
  Future<bool> _requestPermissions() async {
    return AppPermissionService.requestLocationWhenInUsePermissions();
  }

  /// Listens on location change event and fires callback.
  void _onLcationChanged(BaiduLocation location) {
    if (location.latitude != null && location.longitude != null) {
      if (_isFirstLocation && !continuousPositioning) {
        // IMPORTANT!!!
        // There is a big chance that the first location you get is the location that last time app ran.
        // I don't know why and there is no any related official doc.
        // So I have to ignore the first location and wait for next one during singe time positioning action.
        _isFirstLocation = false;
      } else {
        final coordinates =
            BMFCoordinate(location.latitude!, location.longitude!);

        // If location unchanged we do NOT take action.
        if (_lastLocationCoordinate?.latitude != coordinates.latitude ||
            _lastLocationCoordinate?.longitude != coordinates.longitude) {
          locationChanged?.call(MapLocation.fromBaiduLocation(location));

          // Uses this coordinates as last coordinates.
          _lastLocationCoordinate = coordinates;

          if (!continuousPositioning) {
            _locationFlutterPlugin.stopLocation();
          }
        }
      }
    }
  }

  BMF_COORD_TYPE _parseFromBMFLocationCoordType(BMFLocationCoordType type) {
    if (type == BMFLocationCoordType.gcj02) {
      return BMF_COORD_TYPE.COMMON;
    } else if (type == BMFLocationCoordType.bd09ll) {
      return BMF_COORD_TYPE.BD09LL;
    } else {
      return BMF_COORD_TYPE.GPS;
    }
  }
}
